/*
 * callother stand-alone interpreter
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

#include "callother.h"
#include "compiler_unit.h"
#include "eval.h"

//int debug_level = DBG_YACC | DBG_AST | DBG_COMPILE | DBG_EVAL | DBG_STACK;
int debug_level = 0;

static void test_dict()
{
    CoValue *key1;
    CoValue *key2;
    CoValue *value;
    CoValue *dict;

    key1 = CoString_FromString("key");
    key2 = CoString_FromStringAndSize("keyblahblah", 3);
    value = CoString_FromString("value");
    dict = CoDict_New();

    assert(key1->v_ref == 1);
    assert(key2->v_ref == 1);
    assert(value->v_ref == 1);

    CoDict_SetItem(dict, key1, value);
    assert(CoDict_GetItem(dict, key1) == value);
    assert(key1->v_ref == 2);
    assert(key2->v_ref == 1);
    assert(value->v_ref == 2);

    CoDict_SetItem(dict, key2, value);
    assert(CoDict_GetItem(dict, key2) == value);
    assert(key1->v_ref == 2);
    assert(key2->v_ref == 1);
    assert(value->v_ref == 2);

    CoDict_DelItem(dict, key1);
    assert(CoDict_GetItem(dict, key1) == NULL);
    assert(CoDict_GetItem(dict, key2) == NULL);
    assert(key1->v_ref == 1);
    assert(key2->v_ref == 1);
    assert(value->v_ref == 1);

    CoDict_SetItem(dict, key2, value);
    assert(CoDict_GetItem(dict, key2) == value);
    assert(key1->v_ref == 1);
    assert(key2->v_ref == 2);
    assert(value->v_ref == 2);

    CoDict_Clear(dict);
    assert(CoDict_GetItem(dict, key1) == NULL);
    assert(CoDict_GetItem(dict, key2) == NULL);
    assert(key1->v_ref == 1);
    assert(key2->v_ref == 1);
    assert(value->v_ref == 1);

    CO_DECREF(dict);
    CO_DECREF(key1);
    CO_DECREF(key2);
    CO_DECREF(value);
}

static void test_list()
{
    CoValue *list;
    CoValue *item1;
    CoValue *item2;
    CoValue *iter;

    item1 = CoString_FromString("item1");
    item2 = CoString_FromStringAndSize("item2blahblah", 5);
    list = CoList_New(0);

    assert(item1->v_ref == 1);
    assert(item2->v_ref == 1);
    assert(list->v_ref == 1);

    CoList_Append(list, item1);
    assert(item1->v_ref == 2);
    assert(CoList_GetItem(list, 0) == item1);

    CoList_SetItem(list, 0, item2);
    assert(item1->v_ref == 1);
    /* CoList_SetItem doesn't increase reference count!  */
    assert(item2->v_ref == 1);
    assert(CoList_GetItem(list, 0) == item2);

    CoList_Append(list, item1);
    iter = CoValue_GetIter(list);
    assert(list->v_ref == 2);
    assert(CoIter_Next(iter) == item2);
    assert(item2->v_ref == 2);
    assert(CoIter_Next(iter) == item1);
    assert(item1->v_ref == 3);
    assert(CoIter_Next(iter) == NULL);

    CO_DECREF(iter);
    CO_DECREF(list);
    CO_DECREF(item1);
    CO_DECREF(item1);
    CO_DECREF(item2);
}

static void test_int()
{
    CoValue *num[200];
    CoValue *birth;
    int i;

    birth = NULL;
    for (i = 0; i < 200; i++) {
        num[i] = CoInt_FromInt(i);
        assert(num[i]->v_ref == 1);
        assert(i == ((CoInt *)num[i])->i_val);
    }
    birth = num[119];
    CO_DECREF(birth);
    num[119] = CoInt_FromInt(911);
    assert(birth == num[119]);

    for (i = 0; i < 200; i++) {
        CO_DECREF(num[i]);
    }
}

static int cmpfunc(const void *a, const void *b)
{
    return CoInt_AsInt(*(CoValue **)a) - CoInt_AsInt(*(CoValue **)b);
}

static void test_list_sort()
{
    CoValue *list;
    CoValue *num1;
    CoValue *num2;
    CoValue *num3;
    CoValue *num4;

    list = CoList_New(4);
    num1 = CoInt_FromInt(1);
    num2 = CoInt_FromInt(3);
    num3 = CoInt_FromInt(5);
    num4 = CoInt_FromInt(7);

    CoList_SetItem(list, 0, num2);
    CoList_SetItem(list, 1, num4);
    CoList_SetItem(list, 2, num3);
    CoList_SetItem(list, 3, num1);

    CoList_SortItems(list, cmpfunc);

    assert(CoList_GetItem(list, 0) == num1);
    assert(CoList_GetItem(list, 1) == num2);
    assert(CoList_GetItem(list, 2) == num3);
    assert(CoList_GetItem(list, 3) == num4);

    CO_DECREF(list);
}

static void test_str()
{
    CoValue *v;
    CoValue *str;

    v = CoInt_FromInt(911);
    str = CoValue_Str(v);
    CO_DECREF(v);
    assert(strcmp(CoString_AS_STRING(str), "911") == 0);
    CO_DECREF(str);

    v = CoString_FromString("I am strong");
    str = CoValue_Str(v);
    CO_DECREF(v);
    assert(strcmp(CoString_AS_STRING(str), "I am strong") == 0);
    CO_DECREF(str);

    v = CoList_New(0);
    str = CoValue_Str(v);
    CO_DECREF(v);
    CO_DECREF(str);

    v = CoDict_New();
    str = CoValue_Str(v);
    CO_DECREF(v);
    CO_DECREF(str);
}

typedef void (*TestCase)();
TestCase g_tests[] = {
    test_int,
    test_str,
    test_list,
    test_dict,
    test_list_sort,
};

void test_eval(char *path)
{
    struct stat statbuf;

    if (lstat(path, &statbuf) < 0) {
        printf("stat error for %s\n", path);
        return;
    }

    if (S_ISDIR(statbuf.st_mode)) {
        char fullpath[1024 + 2];
        int dir_len;
        int file_len;
        struct dirent *dirp;
        DIR *dp;

        dir_len = strlen(path);
        if (dir_len > 1024) {
            printf("path too long: %s\n", path);
            return;
        }
        strncpy(fullpath, path, dir_len);
        if (fullpath[dir_len-1] != '/') {
            fullpath[dir_len++] = '/';
        }
        fullpath[dir_len] = 0;

        dp = opendir(fullpath);
        if (dp == NULL) {
            printf("can't read directory %s\n", path);
            return;
        }

        while ((dirp = readdir(dp)) != NULL) {
            if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) {
                continue;
            }

            file_len = strlen(dirp->d_name);
            if (file_len > 1024 - dir_len) {
                printf("path too long: %s%s\n", path, dirp->d_name);
                continue;
            }

            strncpy(fullpath + dir_len, dirp->d_name, file_len);
            fullpath[dir_len + file_len] = 0;

            test_eval(fullpath);
        }

        if (closedir(dp) < 0) {
            printf("can't close directory %s\n", path);
        }
    }
    else if (S_ISREG(statbuf.st_mode)) {
        int len;
        CoValue *mod;

        len = strlen(path);
        if (len < 3 || path[len-2] != '.' || path[len-1] != 'c')
        {
            printf("%s is not a source file\n", path);
            return;
        }

        path[len-2] = 0;

        mod = Eval_FindModule(path);
        CO_DECREF(mod);
    }
    else {
            printf("%s is neither a source file or a directory\n", path);
    }
}

int main(int argc, char **argv)
{
    int i;
    char *path;

    for (i = 0; i < sizeof(g_tests) / sizeof(g_tests[0]); i++)
    {
        (*g_tests[i])();
    }

    if (argc == 2) {
        path = argv[1];
    }
    else {
        path = "test/cofiles";
    }

    Compiler_Init();
    Eval_Init();

    test_eval(path);

    Compiler_Destruct();
    Eval_Destruct();

#ifdef CO_COUNT_ALLOCS
    extern void dump_counts(FILE* f);
    dump_counts(stdout);
#endif

    return 0;
}

